(*
    Title:      Standard Basis Library: Pack Real structures and signatures
    Author:     David Matthews
    Copyright   David Matthews 2000, 2015

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License version 2.1 as published by the Free Software Foundation.
    
    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.
    
    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*)

signature PACK_REAL =
sig
    type real
    val bytesPerElem : int
    val isBigEndian : bool
    val toBytes   : real -> Word8Vector.vector
    val fromBytes : Word8Vector.vector -> real
    val subVec : Word8Vector.vector * int -> real
    val subArr : Word8Array.array * int -> real
    val update : Word8Array.array * int * real -> unit
end;

local
    open LibrarySupport
    open LibrarySupport.Word8Array

    local
        val realSizeCall: unit -> word = RunCall.rtsCallFast1 "PolyRealSize"
    in
        val realSize: word = realSizeCall ()
    end

    local
        val System_move_bytes: address*address*word*word*word->unit = RunCall.moveBytes

        (* Move bytes, reversing the order. *)
        fun swapOrder(src: address, srcOff: word,
                      dest: address, destOff: word,
                      length: word) =
            if length = 0w0 then ()
            else
            (
                RunCall.storeByte(dest, destOff+length-0w1, RunCall.loadByte(src, srcOff));
                swapOrder(src, srcOff+0w1, dest, destOff, length-0w1)
            )
    in
        fun doMove(src: address, srcOff: word,
                   dest: address, destOff: word, wantBigEndian: bool) =
            if wantBigEndian = bigEndian (* Host byte order = required byte order *)
            then System_move_bytes(src, dest, srcOff, destOff, realSize)
            else (* Host byte order is reverse of required byte order. *)
                swapOrder(src, srcOff, dest, destOff, realSize)
    end
in

    structure PackRealBig: PACK_REAL =
    struct
        type real = real

        val bytesPerElem: int = Word.toInt realSize
        val isBigEndian = true (* Note: this seems unnecessary. *)
    
        fun toBytes r =
        let
            val v = allocString realSize
            (* r is actually represented by a pointer to a vector. *)
            val addr: address = RunCall.unsafeCast r
        in
            doMove(addr, 0w0, stringAsAddress v, wordSize, isBigEndian);
            RunCall.clearMutableBit v;
            w8vectorFromString v
        end

        fun fromBytes v =
        (* Raises an exception if the vector is too small and takes the first
           few elements if it's larger. *)
            if Word8Vector.length v < bytesPerElem
            then raise Subscript
            else
            let
                val r = allocBytes realSize
            in
                doMove(w8vectorAsAddress v, wordSize, r, 0w0, isBigEndian);
                RunCall.clearMutableBit r;
                (RunCall.unsafeCast r): real
            end

        fun subVec(v, i) =
        let
            val iW = unsignedShortOrRaiseSubscript i * realSize
        in
            if iW >= Word.fromInt(Word8Vector.length v)
            then raise Subscript (* This IS defined. *)
            else
            let
                val r = allocBytes realSize
            in
                doMove(w8vectorAsAddress v, wordSize + iW, r, 0w0, isBigEndian);
                RunCall.clearMutableBit r;
                (RunCall.unsafeCast r): real
            end
        end

        fun subArr(Array(l, v), i) =
        let
            val iW = unsignedShortOrRaiseSubscript i * realSize
        in
            if iW >= l
            then raise Subscript (* This IS defined. *)
            else
            let
                val r = allocBytes realSize
            in
                doMove(v, iW, r, 0w0, isBigEndian);
                RunCall.clearMutableBit r;
                (RunCall.unsafeCast r): real
            end
        end

        fun update(Array(l, v), i, r) =
        let
            val iW = unsignedShortOrRaiseSubscript i * realSize
        in
            if iW >= l
            then raise Subscript (* This IS defined. *)
            else
            let
                (* r is actually represented by a pointer to a vector. *)
                val addr: address = RunCall.unsafeCast r
            in
                doMove(addr, 0w0, v, iW, isBigEndian)
            end
        end
    end;
    
    structure PackRealLittle: PACK_REAL =
    struct
        type real = real
        val bytesPerElem: int = Word.toInt realSize
        val isBigEndian = false
        fun toBytes r =
        let
            val v = allocString realSize
            (* r is actually represented by a pointer to a vector. *)
            val addr: address = RunCall.unsafeCast r
        in
            doMove(addr, 0w0, stringAsAddress v, wordSize, isBigEndian);
            RunCall.clearMutableBit v;
            w8vectorFromString v
        end

        fun fromBytes v =
        (* Raises an exception if the vector is too small and takes the first
           few elements if it's larger. *)
            if Word8Vector.length v < bytesPerElem
            then raise Subscript
            else
            let
                val r = allocBytes realSize
            in
                doMove(w8vectorAsAddress v, wordSize, r, 0w0, isBigEndian);
                RunCall.clearMutableBit r;
                (RunCall.unsafeCast r): real
            end

        fun subVec(v, i) =
        let
            val iW = unsignedShortOrRaiseSubscript i * realSize
        in
            if iW >= Word.fromInt(Word8Vector.length v)
            then raise Subscript (* This IS defined. *)
            else
            let
                val r = allocBytes realSize
            in
                doMove(w8vectorAsAddress v, wordSize+iW, r, 0w0, isBigEndian);
                RunCall.clearMutableBit r;
                (RunCall.unsafeCast r): real
            end
        end

        fun subArr(Array(l, v), i) =
        let
            val iW = unsignedShortOrRaiseSubscript i * realSize
        in
            if iW >= l
            then raise Subscript (* This IS defined. *)
            else
            let
                val r = allocBytes realSize
            in
                doMove(v, iW, r, 0w0, isBigEndian);
                RunCall.clearMutableBit r;
                (RunCall.unsafeCast r): real
            end
        end

        fun update(Array(l, v), i, r) =
        let
            val iW = unsignedShortOrRaiseSubscript i * realSize
        in
            if iW >= l
            then raise Subscript (* This IS defined. *)
            else
            let
                (* r is actually represented by a pointer to a vector. *)
                val addr: address = RunCall.unsafeCast r
            in
                doMove(addr, 0w0, v, iW, isBigEndian)
            end
        end
    end;
end;
